﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TypeHelper.cs" company="">
//   
// </copyright>
// <summary>
//   The type helper.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace Skymate.Utilities
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;

    using Fasterflect;

    using Skymate.Extensions;

    /// <summary>
    ///     The type helper.
    /// </summary>
    public static class TypeHelper
    {
        /// <summary>
        /// The creator.
        /// </summary>
        /// <typeparam name="T">
        /// </typeparam>
        public delegate T Creator<T>();

        /// <summary>
        /// The is unitialized value.
        /// </summary>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        public static bool IsUnitializedValue(object value)
        {
            if (value == null)
            {
                return true;
            }

            var unitializedValue = CreateUnitializedValue(value.GetType());
            return value.Equals(unitializedValue);
        }

        /// <summary>
        /// The create unitialized value.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <returns>
        /// The <see cref="object"/>.
        /// </returns>
        /// <exception cref="ArgumentException">
        /// </exception>
        public static object CreateUnitializedValue(Type type)
        {
            Guard.ArgumentNotNull(type, "type");

            if (type.IsGenericTypeDefinition)
            {
                throw new ArgumentException(
                    "Type {0} is a generic type definition and cannot be instantiated.".FormatWith(
                        CultureInfo.InvariantCulture, 
                        type), 
                    "type");
            }

            if (type.IsClass || type.IsInterface || type == typeof(void))
            {
                return null;
            }

            if (type.IsValueType)
            {
                return Activator.CreateInstance(type);
            }

            throw new ArgumentException(
                "Type {0} cannot be instantiated.".FormatWith(CultureInfo.InvariantCulture, type), 
                "type");
        }

        /// <summary>
        /// The get element type.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <returns>
        /// The <see cref="Type"/>.
        /// </returns>
        public static Type GetElementType(Type type)
        {
            if (!type.IsPredefinedSimpleType())
            {
                if (type.HasElementType)
                {
                    return GetElementType(type.GetElementType());
                }

                if (type.IsPredefinedGenericType())
                {
                    return GetElementType(type.GetGenericArguments()[0]);
                }

                var type2 = type.FindIEnumerable();
                if (type2 != null)
                {
                    var type3 = type2.GetGenericArguments()[0];
                    return GetElementType(type3);
                }
            }

            return type;
        }

        /// <summary>
        /// The get dictionary key value types.
        /// </summary>
        /// <param name="dictionaryType">
        /// The dictionary type.
        /// </param>
        /// <param name="keyType">
        /// The key type.
        /// </param>
        /// <param name="valueType">
        /// The value type.
        /// </param>
        /// <exception cref="Exception">
        /// </exception>
        public static void GetDictionaryKeyValueTypes(Type dictionaryType, out Type keyType, out Type valueType)
        {
            Guard.ArgumentNotNull(dictionaryType, "type");

            Type genericDictionaryType;
            if (dictionaryType.IsSubClass(typeof(IDictionary<,>), out genericDictionaryType))
            {
                if (genericDictionaryType.IsGenericTypeDefinition)
                {
                    throw new Exception("Type {0} is not a dictionary.".FormatInvariant(dictionaryType));
                }

                var dictionaryGenericArguments = genericDictionaryType.GetGenericArguments();

                keyType = dictionaryGenericArguments[0];
                valueType = dictionaryGenericArguments[1];
            }
            else if (typeof(IDictionary).IsAssignableFrom(dictionaryType))
            {
                keyType = null;
                valueType = null;
            }
            else
            {
                throw new Exception("Type {0} is not a dictionary.".FormatInvariant(dictionaryType));
            }
        }

        /// <summary>
        /// The get dictionary value type.
        /// </summary>
        /// <param name="dictionaryType">
        /// The dictionary type.
        /// </param>
        /// <returns>
        /// The <see cref="Type"/>.
        /// </returns>
        public static Type GetDictionaryValueType(Type dictionaryType)
        {
            Type keyType;
            Type valueType;
            GetDictionaryKeyValueTypes(dictionaryType, out keyType, out valueType);

            return valueType;
        }

        /// <summary>
        /// The get dictionary key type.
        /// </summary>
        /// <param name="dictionaryType">
        /// The dictionary type.
        /// </param>
        /// <returns>
        /// The <see cref="Type"/>.
        /// </returns>
        public static Type GetDictionaryKeyType(Type dictionaryType)
        {
            Type keyType;
            Type valueType;
            GetDictionaryKeyValueTypes(dictionaryType, out keyType, out valueType);

            return keyType;
        }

        /// <summary>
        /// Tests whether the list's items are their unitialized value.
        /// </summary>
        /// <param name="list">
        /// The list.
        /// </param>
        /// <returns>
        /// Whether the list's items are their unitialized value
        /// </returns>
        public static bool ItemsUnitializedValue<T>(IList<T> list)
        {
            Guard.ArgumentNotNull(list, "list");

            var elementType = GetElementType(list.GetType()); // GetListItemType(list.GetType());

            if (elementType.IsValueType)
            {
                var unitializedValue = CreateUnitializedValue(elementType);

                for (var i = 0; i < list.Count; i++)
                {
                    if (!list[i].Equals(unitializedValue))
                    {
                        return false;
                    }
                }
            }
            else if (elementType.IsClass)
            {
                for (var i = 0; i < list.Count; i++)
                {
                    object value = list[i];

                    if (value != null)
                    {
                        return false;
                    }
                }
            }
            else
            {
                throw new Exception(
                    "Type {0} is neither a ValueType or a Class.".FormatWith(CultureInfo.InvariantCulture, elementType));
            }

            return true;
        }

        /// <summary>
        /// Gets the member's underlying type.
        /// </summary>
        /// <param name="member">
        /// The member.
        /// </param>
        /// <returns>
        /// The underlying type of the member.
        /// </returns>
        public static Type GetMemberUnderlyingType(MemberInfo member)
        {
            Guard.ArgumentNotNull(member, "member");

            switch (member.MemberType)
            {
                case MemberTypes.Field:
                    return ((FieldInfo)member).FieldType;
                case MemberTypes.Property:
                    return ((PropertyInfo)member).PropertyType;
                case MemberTypes.Event:
                    return ((EventInfo)member).EventHandlerType;
                default:
                    throw new ArgumentException(
                        "MemberInfo must be if type FieldInfo, PropertyInfo or EventInfo", 
                        "member");
            }
        }

        /// <summary>
        /// The get property names.
        /// </summary>
        /// <param name="target">
        /// The target.
        /// </param>
        /// <returns>
        /// The <see cref="IList"/>.
        /// </returns>
        public static IList<string> GetPropertyNames(object target)
        {
            return target.GetType().GetProperties().Select(z => z.Name).ToList();
        }

        /// <summary>
        /// The get fields and properties.
        /// </summary>
        /// <param name="bindingAttr">
        /// The binding attr.
        /// </param>
        /// <typeparam name="T">
        /// </typeparam>
        /// <returns>
        /// The <see cref="IEnumerable"/>.
        /// </returns>
        public static IEnumerable<MemberInfo> GetFieldsAndProperties<T>(BindingFlags bindingAttr)
        {
            return typeof(T).GetFieldsAndProperties(bindingAttr);
        }

        /// <summary>
        /// The get attribute.
        /// </summary>
        /// <param name="attributeProvider">
        /// The attribute provider.
        /// </param>
        /// <typeparam name="T">
        /// </typeparam>
        /// <returns>
        /// The <see cref="T"/>.
        /// </returns>
        public static T GetAttribute<T>(ICustomAttributeProvider attributeProvider) where T : Attribute
        {
            return GetAttribute<T>(attributeProvider, true);
        }

        /// <summary>
        /// The get attribute.
        /// </summary>
        /// <param name="attributeProvider">
        /// The attribute provider.
        /// </param>
        /// <param name="inherit">
        /// The inherit.
        /// </param>
        /// <typeparam name="T">
        /// </typeparam>
        /// <returns>
        /// The <see cref="T"/>.
        /// </returns>
        public static T GetAttribute<T>(ICustomAttributeProvider attributeProvider, bool inherit) where T : Attribute
        {
            var attributes = GetAttributes<T>(attributeProvider, inherit);
            return attributes.FirstOrDefault();
        }

        /// <summary>
        /// The get attributes.
        /// </summary>
        /// <param name="attributeProvider">
        /// The attribute provider.
        /// </param>
        /// <param name="inherit">
        /// The inherit.
        /// </param>
        /// <typeparam name="T">
        /// </typeparam>
        /// <returns>
        /// The <see cref="T[]"/>.
        /// </returns>
        public static T[] GetAttributes<T>(ICustomAttributeProvider attributeProvider, bool inherit) where T : Attribute
        {
            Guard.ArgumentNotNull(attributeProvider, "attributeProvider");

            return (T[])attributeProvider.GetCustomAttributes(typeof(T), inherit);
        }

        /// <summary>
        /// Gets the value of a property through reflection.
        /// </summary>
        /// <param name="from">
        /// The <see cref="object"/> to get the value from.
        /// </param>
        /// <param name="propertyName">
        /// The name of the property to extract the value for.
        /// </param>
        /// <returns>
        /// The value of the property.
        /// </returns>
        public static object GetPropertyValue(object from, string propertyName)
        {
            Guard.ArgumentNotNull(from, "value");
            var propertyInfo = from.GetType()
                .GetProperty(
                    propertyName, 
                    BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static
                    | BindingFlags.NonPublic);
            return propertyInfo.GetValue(from, null);
        }

        /// <summary>
        /// The try action.
        /// </summary>
        /// <param name="creator">
        /// The creator.
        /// </param>
        /// <param name="output">
        /// The output.
        /// </param>
        /// <typeparam name="T">
        /// </typeparam>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        public static bool TryAction<T>(Creator<T> creator, out T output)
        {
            Guard.ArgumentNotNull(creator, "creator");

            try
            {
                output = creator();
                return true;
            }
            catch
            {
                output = default(T);
                return false;
            }
        }

        /// <summary>
        /// The is primitive extended.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        private static bool IsPrimitiveExtended(Type type)
        {
            if (type.IsPrimitive)
            {
                return true;
            }

            return type == typeof(string) || type == typeof(decimal) || type == typeof(DateTime)
                   || type == typeof(DateTimeOffset) || type == typeof(TimeSpan) || type == typeof(Guid);
        }

        /// <summary>
        /// The is primitive extended including nullable.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        public static bool IsPrimitiveExtendedIncludingNullable(Type type)
        {
            if (IsPrimitiveExtended(type))
            {
                return true;
            }

            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                return IsPrimitiveExtended(type.GenericTypeArguments[0]);
            }

            return false;
        }

        /// <summary>
        /// The try get description.
        /// </summary>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <param name="description">
        /// The description.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        public static bool TryGetDescription(object value, out string description)
        {
            return TryAction(delegate { return GetDescription(value); }, out description);
        }

        /// <summary>
        /// The get description.
        /// </summary>
        /// <param name="o">
        /// The o.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        /// <exception cref="Exception">
        /// </exception>
        public static string GetDescription(object o)
        {
            Guard.ArgumentNotNull(o, "o");

            var attributeProvider = o as ICustomAttributeProvider;

            // object passed in isn't an attribute provider
            // if value is an enum value, get value field member, otherwise get values type
            if (attributeProvider == null)
            {
                var valueType = o.GetType();

                if (valueType.IsEnum)
                {
                    attributeProvider = valueType.GetField(o.ToString());
                }
                else
                {
                    attributeProvider = valueType;
                }
            }

            var descriptionAttribute = GetAttribute<DescriptionAttribute>(attributeProvider);

            if (descriptionAttribute != null)
            {
                return descriptionAttribute.Description;
            }

            throw new Exception(
                "No DescriptionAttribute on '{0}'.".FormatWith(CultureInfo.InvariantCulture, o.GetType()));
        }

        /// <summary>
        /// The get descriptions.
        /// </summary>
        /// <param name="values">
        /// The values.
        /// </param>
        /// <returns>
        /// The <see cref="IList"/>.
        /// </returns>
        public static IList<string> GetDescriptions(IList values)
        {
            Guard.ArgumentNotNull(values, "values");

            var descriptions = new string[values.Count];

            for (var i = 0; i < values.Count; i++)
            {
                descriptions[i] = GetDescription(values[i]);
            }

            return descriptions;
        }

        /// <summary>
        /// </summary>
        /// <param name="type">
        /// The type from which to infer namespace name.
        /// </param>
        /// <param name="resourceName">
        /// The relative name of the resource, without (default) namespace name.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        public static string GetStringFromEmbeddedResource(Type type, string resourceName)
        {
            var assembly = type.Assembly;
            resourceName = string.Concat(type.Namespace, ".", resourceName);
            return GetStringFromEmbeddedResource(assembly, resourceName);
        }

        /// <summary>
        /// The get string from embedded resource.
        /// </summary>
        /// <param name="assembly">
        /// The assembly.
        /// </param>
        /// <param name="resourceFullName">
        /// The resource full name.
        /// </param>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        /// <exception cref="Exception">
        /// </exception>
        public static string GetStringFromEmbeddedResource(Assembly assembly, string resourceFullName)
        {
            using (var stream = assembly.GetManifestResourceStream(resourceFullName))
            {
                try
                {
                    return stream.AsString();

                    // using (StreamReader reader = new StreamReader(stream))
                    // {
                    // return reader.ReadToEnd();
                    // }
                }
                catch (Exception e)
                {
                    throw new Exception("Error retrieving from Resources. Tried '" + resourceFullName + "'\r\n" + e);
                }
            }
        }

        /// <summary>
        /// The get sequence type.
        /// </summary>
        /// <param name="elementType">
        /// The element type.
        /// </param>
        /// <returns>
        /// The <see cref="Type"/>.
        /// </returns>
        public static Type GetSequenceType(Type elementType)
        {
            return typeof(IEnumerable<>).MakeGenericType(elementType);
        }

        /// <summary>
        /// The get member type.
        /// </summary>
        /// <param name="mi">
        /// The mi.
        /// </param>
        /// <returns>
        /// The <see cref="Type"/>.
        /// </returns>
        public static Type GetMemberType(MemberInfo mi)
        {
            var fi = mi as FieldInfo;
            if (fi != null)
            {
                return fi.FieldType;
            }

            var pi = mi as PropertyInfo;
            if (pi != null)
            {
                return pi.PropertyType;
            }

            var ei = mi as EventInfo;
            if (ei != null)
            {
                return ei.EventHandlerType;
            }

            return null;
        }

        /// <summary>
        ///     The register type converter.
        /// </summary>
        /// <typeparam name="T">
        /// </typeparam>
        /// <typeparam name="TC">
        /// </typeparam>
        public static void RegisterTypeConverter<T, TC>() where TC : TypeConverter
        {
            TypeDescriptor.AddAttributes(typeof(T), new TypeConverterAttribute(typeof(TC)));
        }

        /// <summary>
        /// The create instance.
        /// </summary>
        /// <param name="parameters">
        /// The parameters.
        /// </param>
        /// <typeparam name="T">
        /// </typeparam>
        /// <returns>
        /// The <see cref="T"/>.
        /// </returns>
        public static T CreateInstance<T>(params object[] parameters) where T : class
        {
            return (T)typeof(T).CreateInstance(parameters);
        }

        /// <summary>
        /// The create instance.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <param name="parameters">
        /// The parameters.
        /// </param>
        /// <returns>
        /// The <see cref="object"/>.
        /// </returns>
        public static object CreateInstance(Type type, params object[] parameters)
        {
            return type.CreateInstance(parameters);
        }
    }
}